package util;

import java.util.Locale;
import static util.Constant.INT_PHONETIC_SEUIL;
/**
 * Cette classe permet de comparer deux chaînes de caractères. Elle implémente
 * un algorithme (Phonex) pour la phonétique et utilise aussi la méthode de
 * traduction de chaînes de caractères.
 * 
 * @author Guillaume Poussel
 * 
 */
public class Comparator {

    /**
     * Algorithme Phonex Copyright Frédéric BROUARD (31/3/99) Cet algorithme
     * établit une chaîne de caractères représentant la phonétique d'une autre
     * chaîne de caractères.
     * 
     * http://sqlpro.developpez.com/cours/soundex/#L5
     * 
     * @param chaine1
     * @return a string representation.
     */
    private static String phonex(String chaine1) {
        String chaine = chaine1.toUpperCase(Locale.FRENCH);

        // 1. remplacer les y par des i
        chaine = chaine.replace('Y', 'I');

        // 2. Supprimer les h qui ne sont pas précédés de c ou de s ou de p
        chaine = chaine.replaceAll("([^CSP])H", "$1");

        // 3. remplacement du ph par f
        chaine = chaine.replaceAll("PH", "F");

        // 4.
        chaine = chaine.replaceAll("G(AN|AM|AIN|AIM)", "K$1");

        // 5.
        chaine = chaine.replaceAll("((A|E)I(N|M))([AEIOU]{1})", "YN$4");

        // 6.
        chaine = chaine.replaceAll("EAU", "O");
        chaine = chaine.replaceAll("OUA", "2");
        chaine = chaine.replaceAll("(A|E)I(N|M)", "4");

        // 7.
        chaine = chaine.replaceAll("(É|È|Ê|EI|AI)", "Y");
        chaine = chaine.replaceAll("E(R|SS|T)", "Y$1");

        // 8.
        chaine = chaine.replaceAll("(A|E)(N|M)[^AEIOU1-4]", "1");
        chaine = chaine.replaceAll("IN", "4");

        // 9.
        chaine = chaine.replaceAll("([AEIOU1-4]{1})S([AEIOU1-4]{1})", "$1Z$2");

        // 10.
        chaine = chaine.replaceAll("(OE|EU)", "E");
        chaine = chaine.replaceAll("AU", "O");
        chaine = chaine.replaceAll("O(I|Y)", "2");
        chaine = chaine.replaceAll("OU", "3");

        // 11.
        chaine = chaine.replaceAll("(S|C|SC)H", "5");
        chaine = chaine.replaceAll("S(S|C)", "S");

        // 12.
        chaine = chaine.replaceAll("C(E|I)", "S");

        // 13.
        chaine = chaine.replaceAll("(C|QU|GU|Q)", "K");
        chaine = chaine.replaceAll("G(A|O|Y)", "K$1");

        // 14.
        chaine = chaine.replace('A', 'O');
        chaine = chaine.replace('D', 'T');
        chaine = chaine.replace('P', 'T');
        chaine = chaine.replace('J', 'G');
        chaine = chaine.replace('B', 'F');
        chaine = chaine.replace('V', 'F');
        chaine = chaine.replace('M', 'N');

        // 15.
        chaine = chaine.replaceAll("(.)\\1*", "$1");

        // 16.
        if (chaine.endsWith("T") || chaine.endsWith("X"))
            chaine = chaine.substring(0, chaine.length() - 1);

        return chaine;
    }

    /**
     * Cette méthode est la seule méthode publique de cette classe. Elle permet
     * de se substituer à .equals() pour comparer des chaînes de caractères par
     * rapport à la phonétique et à la langue.
     * 
     * 
     * Cette méthode utilise une constante pour connaître le seuil à partir
     * duquel des chaînes de caractères sont considérées proches au niveau
     * phonétique.
     * 
     * @param chaine1 La première chaîne à comparer
     * @param chaine2 La seconde chaîne
     * @return <code>true</code> si deux chaînes sont considérées égales,
     *         <code>false</code< sinon.
     * @throws Exception Si on n'a pas Internet, une exception est levée.
     */
    public static boolean compare(String chaine1, String chaine2) throws Exception {
        return (phonexDistance(chaine1, chaine2) >= INT_PHONETIC_SEUIL)
                || Translator.equivalent(chaine1, chaine2);
    }

    /**
     * Cette méthode calcule la distance entre deux chaînes de caractères en
     * fonction de leur phonétique française. Il s'agit en fait de la distance
     * de Leveinshtein de leur représentation Phonex.
     * 
     * @param chaine1 Première chaine
     * @param chaine2 Seconde chaine
     * @return Un nombre entier représentant le pourcentage de correspondance
     *         entre deux chaînes de caractères. Plus le nombre est proche de
     *         100, plus les chaînes de caractères ont une pronociation
     *         similaire.
     */
    private static int phonexDistance(String chaine1, String chaine2) {
        String soundex1 = phonex(chaine1);
        String soundex2 = phonex(chaine2);

        // the distance is the cost for transforming all letters in both strings
        return 100 - (200 * levenshteinDistance(soundex1, soundex2))
                / (chaine1.length() + chaine2.length());
    }

    /**
     * Cette méthode calcule la distance de Levenshtein entre deux chaînes de
     * caractères. L'algorithme de distance permet de connaître le nombre
     * d'opérations nécessaires pour passer de la première chaîne à la seconde.
     * Par opération, on entend : remplacement, suppression et ajout d'un
     * caractère
     * 
     * Code source original : WikiBooks
     * 
     * @param str1 La première chaîne
     * @param str2 La seconde chaîne
     * @return Un entier compris entre 0 et le maximum entre les longueurs des
     *         deux chaînes
     */
    private static int levenshteinDistance(String str1, String str2) {

        int n = str1.length();
        int m = str2.length();

        if (n == 0)
            return m;
        if (m == 0)
            return n;

        int[][] d = new int[n + 1][m + 1];

        for (int i = 0; i <= n; d[i][0] = i++)
            ;
        for (int j = 1; j <= m; d[0][j] = j++)
            ;

        for (int i = 1; i <= n; i++) {
            char sc = str1.charAt(i - 1);
            for (int j = 1; j <= m; j++) {
                int v = d[i - 1][j - 1];
                if (str2.charAt(j - 1) != sc)
                    v++;
                d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), v);
            }
        }
        return d[n][m];
    }
}
